package online.heycm.codegenerator.service;

import cn.hutool.core.lang.UUID;
import online.heycm.codegenerator.common.error.CodeMsg;
import online.heycm.codegenerator.common.jdbc.Jdbc;
import online.heycm.codegenerator.common.jdbc.UrlTemplate;
import online.heycm.codegenerator.common.session.Session;
import online.heycm.codegenerator.common.session.SessionStorage;
import online.heycm.codegenerator.common.types.DBType;
import online.heycm.codegenerator.common.types.GeneratorType;
import online.heycm.codegenerator.generator.GeneratorSetting;
import online.heycm.codegenerator.generator.MyBatisGenerator;
import online.heycm.codegenerator.generator.MyBatisPlusGenerator;
import online.heycm.codegenerator.service.convert.Convert;
import online.heycm.codegenerator.service.param.ConnectReq;
import online.heycm.codegenerator.service.param.ConnectRes;
import online.heycm.codegenerator.service.param.FileRes;
import online.heycm.codegenerator.service.param.GenerateReq;
import online.heycm.platform.common.entity.constant.Constant;
import online.heycm.platform.common.entity.result.Result;
import online.heycm.platform.common.tools.IOUtil;
import online.heycm.platform.common.tools.file.FileUtil;
import online.heycm.platform.common.tools.file.ZipUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author hey
 * @version 1.0
 * @date 2023/12/21 10:11
 */
@RestController
@RequestMapping
public class GeneratorService {

    @Value("${code-generator-output}")
    private String output;

    @GetMapping("/db/isAlive")
    public Result isAlive(Session session) {
        return session.isAlive() ? Result.success() : Result.error(CodeMsg.INVALID_DB_CONNECTION);
    }

    @PostMapping("/db/connect")
    public Result<ConnectRes> connect(@RequestBody @Valid ConnectReq req, HttpServletRequest request) {
        String oldToken = request.getHeader(Constant.AUTHORIZATION);
        if (StringUtils.hasText(oldToken)) {
            SessionStorage.close(oldToken);
        }
        if (SessionStorage.isFullStorage()) {
            return Result.error(CodeMsg.SESSION_STORAGE_FULL);
        }
        DBType dbType = DBType.of(req.getType());
        String url = UrlTemplate.getUrl(dbType, req.getAddress(), req.getPort().toString(), req.getSchema());
        Connection connection = Jdbc.getConnection(dbType.getDriver(), url, req.getUsername(), req.getPassword());
        String token = UUID.fastUUID().toString().replaceAll("-", "");
        Session session = new Session(token, connection);
        session.setDbType(dbType);
        session.setUrl(url);
        session.setUsername(req.getUsername());
        session.setPassword(req.getPassword());
        session.setOutput(output + "/" + token);
        SessionStorage.put(session);
        ConnectRes res = new ConnectRes(token);
        return Result.success(res);
    }

    @PostMapping("/db/close")
    public Result close(Session session) {
        SessionStorage.close(session);
        return Result.success();
    }

    @GetMapping("/db/tables")
    public Result<List<String>> showTables(Session session) {
        List<String> tables = Jdbc.showTables(session.getConnection());
        return Result.success(tables);
    }

    @PostMapping("/generate")
    public Result generate(@RequestBody @Valid GenerateReq req, Session session) {
        GeneratorSetting setting = Convert.convert(session, req);
        GeneratorType type = GeneratorType.of(req.getGenerator());
        boolean ok;
        if (type == GeneratorType.MyBatis) {
            ok = MyBatisGenerator.generate(setting);
        } else {
            ok = MyBatisPlusGenerator.generate(setting);
        }
        return ok ? Result.success() : Result.error("生成文件失败");
    }

    @GetMapping("/files")
    public Result<List<FileRes>> showFiles(Session session) {
        List<File> files = FileUtil.traverseDirectory(session.getOutput());
        List<FileRes> fileRes = Convert.convertFileRes(files);
        return Result.success(fileRes);
    }

    @GetMapping("/download")
    public void download(Integer[] fileIndex, Session session, HttpServletResponse response) throws IOException {
        // 文件检查
        List<File> files = FileUtil.traverseDirectory(session.getOutput());
        if (CollectionUtils.isEmpty(files)) {
            IOUtil.writeJson(response, Result.error(CodeMsg.INVALID_FILE));
            return;
        }
        // 参数检查
        if (fileIndex == null || fileIndex.length == 0) {
            IOUtil.writeJson(response, Result.error("请选择下载文件"));
            return;
        }
        for (Integer index : fileIndex) {
            if (index == null || index < 0 || index >= files.size()) {
                IOUtil.writeJson(response, Result.error("文件坐标异常，请刷新！"));
                return;
            }
        }
        // 单文件下载
        if (fileIndex.length == 1) {
            File file = files.get(fileIndex[0]);
            IOUtil.writeFile(response, file);
            return;
        }
        // 多文件打包下载
        List<Integer> downloadIdx = Arrays.asList(fileIndex);
        List<File> downloadFiles = new ArrayList<>(files.size());
        for (int i = 0; i < files.size(); i++) {
            if (downloadIdx.contains(i)) {
                downloadFiles.add(files.get(i));
            }
        }
        if (CollectionUtils.isEmpty(downloadFiles)) {
            IOUtil.writeJson(response, Result.error(CodeMsg.INVALID_FILE));
            return;
        }
        File zip = null;
        try {
            zip = new File(session.getOutput() + "/" + System.currentTimeMillis() + ".zip");
            ZipUtil.filesToZip(zip, downloadFiles);
            IOUtil.writeFile(response, zip);
        } finally {
            FileUtil.delete(zip);
        }
    }
}
